//#include <stdio.h>
#include <stdlib.h>

#include "PolygonVolume.h"

namespace Geometry
{
	float RandomFloatRange(float min,float max)
	{
		return (max-min) * ((float)rand() / RAND_MAX ) + min;
	}

	int RandomIntRange(int min,int max)
	{
		return (max-min) * ( rand() / RAND_MAX ) + min;
	}

	PolygonVolume::PolygonVolume()
	{
		_vertexCount = 0;
		_minZ = 0.0f;
		_maxZ = 0.0f;
		_vertices = NULL;
		_version = POLYGONVOLUME_VERSION;
	}

	PolygonVolume::~PolygonVolume()
	{
		if( _vertices != NULL )
		{
			delete[] _vertices;
			_vertices = NULL;
			_vertexCount = 0;
		}
	}

	void PolygonVolume::AllocateVertexes(const unsigned int count)
	{
		if( _vertices != NULL  )
		{
			delete[] _vertices;
		}
		if( count == 0 )
		{
			_vertices = NULL;
			_vertexCount = 0;
			return;
		}
				
		_vertices = new vertex[count];
		_vertexCount = count;

		for(unsigned int i=0;i< _vertexCount;i++)
		{
			_vertices[i].x = 0.0f;
			_vertices[i].y = 0.0f;
			_vertices[i].z = 0.0f;
		}
	}

	void PolygonVolume::SetVertexAt(const unsigned int index,vertex value)
	{
		if( index >= _vertexCount-1 ) return;

		_vertices[index].x = value.x;
		_vertices[index].y = value.y;
		_vertices[index].z = value.z;

		//look for changes in bounding box
		if( value.x > _maxX ) _maxX = value.x;
		if( value.x < _minX ) _minX = value.x;
		if( value.y > _maxY ) _maxY = value.y;
		if( value.y < _minY ) _minY = value.y;
	}

	void PolygonVolume::SetVertices(vertex* vertices,const unsigned int count)
	{
		if( _vertices != NULL )
		{
			delete[] _vertices;
		}
		_vertices = vertices;
		_vertexCount = count;

		CalculateBoundingBox();
	}
	
	void PolygonVolume::SetZBounds(float minZ,float maxZ)
	{
		if(minZ > maxZ)
		{
			_minZ = maxZ;
			_maxZ = minZ;
		}
		else
		{
			_minZ = minZ;
			_maxZ = maxZ;
		}
	}

	int PolygonVolume::EdgesCrossed(vertex test)
	{
		unsigned int i, j;
		int c = 0;
		for (i=0,j=_vertexCount-1;i<_vertexCount;j=i++)
		{
			if( ((_vertices[i].y > test.y) != (_vertices[j].y > test.y)) &&
				(test.x < (_vertices[j].x-_vertices[i].x)*(test.y-_vertices[i].y)/(_vertices[j].y-_vertices[i].y) + _vertices[i].x) )
				c = !c;
		}
		return c;
	}

	void PolygonVolume::CalculateBoundingBox()
	{
		float min_x = 0;
		float max_x = 0;
		float min_y = 0;
		float max_y = 0;

		for(int i=0;i<_vertexCount;i++)
		{
			if( _vertices[i].x > max_x )
			{
				max_x = _vertices[i].x;
			}

			if( _vertices[i].x < min_x )
			{
				min_x = _vertices[i].x;
			}

			if( _vertices[i].y > max_y )
			{
				max_y = _vertices[i].y;
			}

			if( _vertices[i].y < min_y )
			{
				min_y = _vertices[i].y;
			}
		}

		_maxX = max_x;
		_minX = min_x;
		_maxY = max_y;
		_minY = min_y;
	}

	bool PolygonVolume::IsPointInside(vertex test)
	{
		if( (test.z > _maxZ) || (test.z < _minZ) ) return false;
		return EdgesCrossed(test);
	}

	int PolygonVolume::GetVertexCount()
	{
		return _vertexCount;
	}

	vertex PolygonVolume::GetRandomPoint()
	{
		int generationAttempts = 0;
		vertex v;
		v.x = RandomFloatRange(_minX,_maxX);
		v.y = RandomFloatRange(_minY,_maxY);
		v.z = (this->_maxZ + this->_minZ)/2;
		
		while( !IsPointInside(v) && generationAttempts < RANDOM_GEN_ATTEMPTS )
		{		
			v.x = RandomFloatRange(_minX,_maxX);
			v.y = RandomFloatRange(_minY,_maxY);		
			generationAttempts += 1;
		}

		//failsafe in case random loop is not providing a point in poloy
		if(generationAttempts >= RANDOM_GEN_ATTEMPTS)
		{			
			//printf("random attempts exceeded: %d\r\n",generationAttempts);
			v.x = (_maxX - _minX) / 2;
			v.y = (_maxY - _minY) / 2;
		}

		return v;
	}

	bool PolygonVolume::DumpToFile(const char* filename)
	{
		FILE* fp;
		POLYGONVOLUMEHEADER header;

		header.maxZ = _maxZ;
		header.minZ = _minZ;
		header.version = _version;
		header.vertexCount = _vertexCount;

		if( (fp = fopen(filename,"wb")) )
		{
			fwrite(&header,1,sizeof(POLYGONVOLUMEHEADER),fp);
			fwrite(_vertices,_vertexCount,sizeof(vertex),fp);
			fclose(fp);
			return true;
		}
		return false;
	}

	bool PolygonVolume::DumpToFile(FILE* fp)
	{
		POLYGONVOLUMEHEADER header;
		if( fp == NULL ) return false;

		header.maxZ = _maxZ;
		header.minZ = _minZ;
		header.version = _version;
		header.vertexCount = _vertexCount;

		fwrite(&header,1,sizeof(POLYGONVOLUMEHEADER),fp);
		fwrite(_vertices,_vertexCount,sizeof(vertex),fp);
		return true;
	}

	void PolygonVolume::PrintVertexes()
	{
		float x,y;

		printf("Z Bounds: %.2f, %.2f\n",_minZ,_maxZ);

		for(unsigned int i=0;i < _vertexCount;i++)
		{
			x = _vertices[i].x;
			y = _vertices[i].y;
			printf("%d:(%.2f,%.2f)\n",i,x,y);
		}
	}

	PolygonVolume* PolygonVolume::LoadFromFile(const char* filename)
	{
		FILE* fp;
		PolygonVolume* pvol = NULL;
		POLYGONVOLUMEHEADER header;
		int result;

		if( (fp = fopen(filename,"rb")) )
		{
			pvol = new PolygonVolume();
			result = fread(&header,sizeof(POLYGONVOLUMEHEADER),1,fp);

			if( result != 1 )
			{
				delete pvol;
				pvol=NULL;
			}

			if( pvol != NULL )
			{
				pvol->_vertexCount = header.vertexCount;
				pvol->_minZ = header.minZ;
				pvol->_maxZ = header.maxZ;
				pvol->_version = header.version;
				pvol->SetVertices(new vertex[header.vertexCount],header.vertexCount);

				result = fread(pvol->_vertices,sizeof(vertex),pvol->_vertexCount,fp);

				if( result != (int)pvol->_vertexCount )
				{
					delete pvol;
					pvol=NULL;
				}
			}
			fclose(fp);
		}

		return pvol;
	}

	PolygonVolume* PolygonVolume::LoadFromFile(FILE* fp)
	{
		if( fp == NULL ) return NULL;

		PolygonVolume* pvol = new PolygonVolume();
		POLYGONVOLUMEHEADER header;
		int result;

		result = fread(&header,sizeof(POLYGONVOLUMEHEADER),1,fp);

		if( result != 1 )
		{
			delete pvol;
			pvol=NULL;
		}

		if( pvol != NULL )
		{
			pvol->_vertexCount = header.vertexCount;
			pvol->_minZ = header.minZ;
			pvol->_maxZ = header.maxZ;
			pvol->_version = header.version;

			pvol->AllocateVertexes(pvol->_vertexCount);

			result = fread(pvol->_vertices,sizeof(vertex),pvol->_vertexCount,fp);

			if( result != (int)pvol->_vertexCount )
			{
				delete pvol;
				pvol=NULL;
			}
		}
		return pvol;
	}

	/////////////////////////////
	/* Manager implementation */
	////////////////////////////
	PolygonVolumeManager::PolygonVolumeManager()
	{
		_polygonCount = 0;
		_polygons = NULL;
	}

	PolygonVolumeManager::~PolygonVolumeManager()
	{
		DeallocatePolygons();
	}

	void PolygonVolumeManager::DeallocatePolygons()
	{
		if( _polygons != NULL )
		{
			for(unsigned int i=0;i<_polygonCount;i++)
			{
				if(_polygons[i] != NULL)
				{
					delete _polygons[i];
				}
			}
			delete[] _polygons;
		}
		_polygonCount = 0;
	}

	void PolygonVolumeManager::AllocatePolygons(unsigned int count)
	{
		DeallocatePolygons();

		_polygonCount = count;
		if( count > 0 )
		{
			this->_polygons = new PolygonVolume*[_polygonCount];
			for(unsigned int i=0;i < _polygonCount;i++)
			{
				_polygons[i] = new PolygonVolume();
			}
		}
	}

	bool PolygonVolumeManager::DumpToFile(const char* filename)
	{
		FILE* fp;
		POLYGONVOLUMEMANAGERHEADER header;

		header.polygonCount = _polygonCount;

		if( (fp = fopen(filename,"wb")) )
		{
			fwrite(&header,1,sizeof(POLYGONVOLUMEMANAGERHEADER),fp);

			for(unsigned int i=0;i<_polygonCount;i++)
			{
				_polygons[i]->DumpToFile(fp);
			}

			fclose(fp);
			return true;
		}
		return false;
	}

	PolygonVolume* PolygonVolumeManager::GetPolygon(unsigned int index)
	{
		if( index >= _polygonCount ) return NULL;
		else return _polygons[index];
	}

	PolygonVolumeManager* PolygonVolumeManager::LoadFromFile(const char* filename)
	{
		FILE* fp;
		PolygonVolumeManager* manager = NULL;
		POLYGONVOLUMEMANAGERHEADER header;
		int result;

		if( (fp = fopen(filename,"rb")) )
		{
			manager = new PolygonVolumeManager();
			result = fread(&header,sizeof(POLYGONVOLUMEMANAGERHEADER),1,fp);

			if( result != 1 )
			{
				delete manager;
				manager=NULL;
			}

			if( manager != NULL )
			{
				manager->_polygonCount = header.polygonCount;
				if(manager->_polygonCount > 0)
				{
					//allocate an array of pointers
					manager->_polygons = new PolygonVolume*[manager->_polygonCount];
					for(unsigned int i=0;i < manager->_polygonCount;i++)
					{
						manager->_polygons[i] = PolygonVolume::LoadFromFile(fp);
						if(manager->_polygons[i] == NULL) manager->_polygons[i] = new PolygonVolume();
					}
				}
				else
				{
					manager->_polygons = NULL;
				}
			}
			fclose(fp);
		}

		return manager;
	}

	void PolygonVolumeManager::Print()
	{
		printf("PolygonVolumeManager: PolygonCount:%d \n",_polygonCount);
		for(unsigned int i=0;i<_polygonCount;i++)
		{
			_polygons[i]->PrintVertexes();
		}
	}

	bool PolygonVolumeManager::TwoPointsInSamePolygon(vertex p1,vertex p2)
	{
		for(unsigned int i=0;i<_polygonCount;i++)
		{
			if( _polygons[i]->IsPointInside(p1) && _polygons[i]->IsPointInside(p2) )
			{
				return true;
			}
		}
		return false;
	}

	bool PolygonVolumeManager::TwoPointsInSamePolygon(float x1,float y1,float z1,float x2,float y2,float z2)
	{
		vertex p1,p2;
		p1.x = x1; p1.y = y1; p1.z = z1;
		p2.x = x2; p2.y = y2; p2.z = z2;
		return TwoPointsInSamePolygon(p1,p2);
	}
}
